This is an R Markdown Notebook. When you execute code within the notebook, the results appear beneath the code.

Try executing this chunk by clicking the Run button within the chunk or by placing your cursor inside it and pressing Ctrl+Shift+Enter.

library(xgboost)

载入程辑包:‘xgboost’

The following object is masked from ‘package:IRanges’:

    slice

The following object is masked from ‘package:plotly’:

    slice

The following object is masked from ‘package:dplyr’:

    slice
library(Matrix)

载入程辑包:‘Matrix’

The following object is masked from ‘package:S4Vectors’:

    expand

The following objects are masked from ‘package:tidyr’:

    expand, pack, unpack
library(mclust)
    __  ___________    __  _____________
   /  |/  / ____/ /   / / / / ___/_  __/
  / /|_/ / /   / /   / / / /\__ \ / /   
 / /  / / /___/ /___/ /_/ /___/ // /    
/_/  /_/\____/_____/\____//____//_/    version 5.4.9
Type 'citation("mclust")' for citing this R package in publications.

分发训练集

umapplot(ds2_AC) + scale_y_continuous(limits = c(-5,15),breaks = NULL) +
        scale_x_continuous(limits = c(-5,15),breaks = NULL)
Scale for 'y' is already present. Adding another scale for 'y', which will replace the existing scale.
Scale for 'x' is already present. Adding another scale for 'x', which will replace the existing scale.
Warning: Removed 113 rows containing missing values (geom_point).

在AC上预训练

AC_data <- get_data_table(ds2_AC, highvar = F, type = "data")
AC_label <- as.numeric(as.character(Idents(ds2_AC)))

set.seed(7)
index <- c(1:dim(AC_data)[2]) %>% sample(ceiling(0.3*dim(AC_data)[2]), replace = F, prob = NULL)

colnames(AC_data) <- NULL

AC_train_data <- list(data = t(as(AC_data[,-index],"dgCMatrix")), label = AC_label[-index])
AC_test_data <- list(data = t(as(AC_data[,index],"dgCMatrix")), label = AC_label[index])

# data(agaricus.train, package='xgboost')

AC_train <- xgb.DMatrix(data = AC_train_data$data,label = AC_train_data$label)
AC_test <- xgb.DMatrix(data = AC_test_data$data,label = AC_test_data$label)

# xgb_params_train = {
#     'objective':'multi:softprob',
#     'eval_metric':'mlogloss',
#     'num_class':self.numbertrainclasses,
#     'eta':0.2,
#     'max_depth':6,
#     'subsample': 0.6}
# nround = 200

watchlist <- list(train = AC_train, eval = AC_test)
xgb_param <- list(eta = 0.2, max_depth = 6, 
                  subsample = 0.6,  num_class = length(table(Idents(ds2_AC))),
                  objective = "multi:softmax", eval_metric = 'mlogloss')

bst_model <- xgb.train(xgb_param, AC_train, nrounds = 200, watchlist, verbose = 0)
xgboost_roc[["auc"]] #只需要这个值
Multi-class area under the curve: 0.9883
adjustedRandIndex(AC_test_data$label, predict_AC_test) #分类器性能
[1] 0.9585312

在PA上训练

PA_data <- get_data_table(ds2_PA, highvar = F, type = "data")
PA_label <- as.numeric(as.character(Idents(ds2_PA)))
set.seed(7)
index <- c(1:dim(PA_data)[2]) %>% sample(ceiling(0.3*dim(PA_data)[2]), replace = F, prob = NULL)
colnames(PA_data) <- NULL

PA_train_data <- list(data = t(as(PA_data[,-index],"dgCMatrix")), label = PA_label[-index])
PA_test_data <- list(data = t(as(PA_data[,index],"dgCMatrix")), label = PA_label[index])

PA_train <- xgb.DMatrix(data = PA_train_data$data,label = PA_train_data$label)
PA_test <- xgb.DMatrix(data = PA_test_data$data,label = PA_test_data$label)

watchlist <- list(train = PA_train, eval = PA_test)
xgb_param <- list(eta = 0.2, max_depth = 6, 
                  subsample = 0.6,  num_class = length(table(Idents(ds2_PA))),
                  objective = "multi:softmax", eval_metric = 'mlogloss')
bst_model <- xgb.train(xgb_param, PA_train, nrounds = 200, watchlist, verbose = 0)
xgboost_roc <- pROC::multiclass.roc(PA_test_data$label, predict_PA_test) #多分类ROC
Setting direction: controls < cases
Setting direction: controls < cases
Setting direction: controls < cases
xgboost_roc[["auc"]]
Multi-class area under the curve: 0.9635
adjustedRandIndex(PA_test_data$label, predict_PA_test) #PA分类器性能
[1] 0.8821278

选择特征common genes of top 500

使用所有来自PA的细胞训练分类器

应用在AC上,计算ARI

selected_features <- intersect(PA_genes$Feature, AC_genes$Feature)
write.csv(selected_features, "./datatable/selected_features.csv", row.names = F)

PA_data <- get_data_table(ds2_PA, highvar = F, type = "data")
PA_data <- PA_data[selected_features,]
PA_label <- as.numeric(as.character(Idents(ds2_PA)))
colnames(PA_data) <- NULL

PA_train_data <- list(data = t(as(PA_data,"dgCMatrix")), label = PA_label)
PA_train <- xgb.DMatrix(data = PA_train_data$data,label = PA_train_data$label)
xgb_param <- list(eta = 0.2, max_depth = 6, 
                  subsample = 0.6,  num_class = length(table(Idents(ds2_PA))),
                  objective = "multi:softmax", eval_metric = 'mlogloss')

bst_model <- xgb.train(xgb_param, PA_train, nrounds = 200, verbose = 1)

# 特征提取
importance <- xgb.importance(colnames(PA_train), model = bst_model)
head(importance)
xgb.ggplot.importance(head(importance,20),n_clusters = 1) + theme_bw()

write.csv(importance, "./datatable/PAtrain_features.csv", row.names = F)

# multi_featureplot(head(importance,9)$Feature, ds2)

应用到AC上

xgboost_roc <- pROC::multiclass.roc(AC_test_data$label, predict_AC_test) #多分类ROC
Setting direction: controls < cases
Setting direction: controls < cases
Setting direction: controls < cases
Setting direction: controls < cases
Setting direction: controls < cases
Setting direction: controls < cases
xgboost_roc[["auc"]]
Multi-class area under the curve: 0.8273
# multi_featureplot(head(importance,4)$Feature, ds2, split.by = "conditions")
# multi_featureplot(head(importance,9)$Feature, ds2_AC)

# 计算ARI 
adjustedRandIndex(predict_AC_test, AC_test_data$label)
[1] 0.3024837
umapplot(ds2,split.by = "conditions")
table(ds2$conditions)

sankey plot

PA -> AC ARI 0.3024837

    pre
true           0           1           2
   0 0.980360065 0.003273322 0.016366612
   1 0.799516908 0.196859903 0.003623188
   2 0.453004622 0.493066256 0.053929122
   3 0.002762431 0.052486188 0.944751381

AC -> PA ARI 0.1797689

    pre
true           0           1           2           3
   0 0.027107438 0.287272727 0.682644628 0.002975207
   1 0.000349895 0.075227432 0.914975507 0.009447166
   2 0.008130081 0.003252033 0.175609756 0.813008130

varify 部分

病变程度量化 # 数据集CA_dataset1 ## 在dataset2的AC上训练 使用top500 in AC, 7:3分发训练集

temp <- get_data_table(ds2_AC, highvar = F, type = "data")
AC_data <- matrix(data=0, nrow = length(AC_genes$Feature), ncol = length(colnames(temp)), byrow = FALSE, dimnames = list(AC_genes$Feature,colnames(temp)))

for(i in intersect(AC_genes$Feature, rownames(temp))){
  AC_data[i,] <- temp[i,]
}
rm(temp)
AC_label <- as.numeric(as.character(Idents(ds2_AC)))
set.seed(7)
index <- c(1:dim(AC_data)[2]) %>% sample(ceiling(0.3*dim(AC_data)[2]), replace = F, prob = NULL)
colnames(AC_data) <- NULL

AC_train_data <- list(data = t(as(AC_data[,-index],"dgCMatrix")), label = AC_label[-index])
AC_test_data <- list(data = t(as(AC_data[,index],"dgCMatrix")), label = AC_label[index])

AC_train <- xgb.DMatrix(data = AC_train_data$data,label = AC_train_data$label)
AC_test <- xgb.DMatrix(data = AC_test_data$data,label = AC_test_data$label)

watchlist <- list(train = AC_train, eval = AC_test)
xgb_param <- list(eta = 0.2, max_depth = 6, 
                  subsample = 0.6,  num_class = length(table(Idents(ds2_AC))),
                  objective = "multi:softmax", eval_metric = 'mlogloss')

bst_model <- xgb.train(xgb_param, AC_train, nrounds = 200, watchlist, verbose = 0)
AC_confuse_matrix_test_prop
    pre
true           0           1           2           3
   0 0.994652406 0.000000000 0.005347594 0.000000000
   1 0.000000000 0.987755102 0.012244898 0.000000000
   2 0.000000000 0.021164021 0.968253968 0.010582011
   3 0.000000000 0.000000000 0.045045045 0.954954955
#ROC曲线
xgboost_roc <- pROC::multiclass.roc(AC_test_data$label, predict_AC_test) #多分类ROC
Setting direction: controls < cases
Setting direction: controls < cases
Setting direction: controls < cases
Setting direction: controls < cases
Setting direction: controls < cases
Setting direction: controls < cases
xgboost_roc[["auc"]]
Multi-class area under the curve: 0.9913
# 计算ARI 
adjustedRandIndex(predict_AC_test, AC_test_data$label)
[1] 0.9648073

temp <- get_data_table(ds1, highvar = F, type = "data")
ds1_data <- matrix(data=0, nrow = length(AC_genes$Feature), ncol = length(colnames(temp)), byrow = FALSE, dimnames = list(AC_genes$Feature,colnames(temp)))
for(i in intersect(AC_genes$Feature, rownames(temp))){
  ds1_data[i,] <- temp[i,]
}
rm(temp)
ds1_label <- as.numeric(as.character(Idents(ds1)))
colnames(ds1_data) <- NULL
ds1_test_data <- list(data = t(as(ds1_data,"dgCMatrix")), label = ds1_label)
ds1_test <- xgb.DMatrix(data = ds1_test_data$data,label = ds1_test_data$label)

#计算混淆矩阵
predict_ds1_test <- round(predict(bst_model, newdata = ds1_test))

ds1_data_confuse_matrix_test <- table(ds1_test_data$label, predict_ds1_test, dnn=c("true","pre"))
ds1_data_confuse_matrix_test_prop <- prop.table(ds1_data_confuse_matrix_test,1)

x <- c("ds1_0", "ds1_1", "ds1_2", "ds1_3")
y <- c("AC_0", "AC_1", "AC_2", "AC_3")

prop <- as.numeric(ds1_data_confuse_matrix_test_prop)
data <- expand.grid(x = x, y = y) %>% bind_cols(prop = prop)
plot <- ggplot(data, aes(x = x, y = y, colour = prop, size = prop)) +
  geom_point()+
  scale_size_continuous(range = c(0, 10)) + 
  labs(x = "clusters", y = "inferred from") + theme_bw()

ggsave("./plots/ACmodel_dataset1.png", plot = plot, device = png, width = 5,height = 4)


ds1_data_confuse_matrix_test
    pre
true    0    1    2    3
   0    0   45 2516    8
   1    4   94  787    7
   2    0    6  474   10
   3    0    1   50  347
ds1_data_confuse_matrix_test_prop  #分析发育轨迹
    pre
true           0           1           2           3
   0 0.000000000 0.017516543 0.979369404 0.003114052
   1 0.004484305 0.105381166 0.882286996 0.007847534
   2 0.000000000 0.012244898 0.967346939 0.020408163
   3 0.000000000 0.002512563 0.125628141 0.871859296
#ROC曲线
xgboost_roc <- pROC::multiclass.roc(ds1_test_data$label, predict_ds1_test) #多分类ROC
Setting direction: controls < cases
Setting direction: controls < cases
Setting direction: controls < cases
Setting direction: controls < cases
Setting direction: controls < cases
Setting direction: controls < cases
xgboost_roc[["auc"]]
Multi-class area under the curve: 0.7198
# 计算ARI 
adjustedRandIndex(predict_ds1_test, ds1_test_data$label)
[1] 0.2321442

冠状动脉数据集

ds0 <- ds0 %>% FindNeighbors(dims = 1:20) %>% FindClusters(resolution = 0.1)
Computing nearest neighbor graph
Computing SNN
Modularity Optimizer version 1.3.0 by Ludo Waltman and Nees Jan van Eck

Number of nodes: 5401
Number of edges: 173943

Running Louvain algorithm...
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Maximum modularity in 10 random starts: 0.9544
Number of communities: 5
Elapsed time: 0 seconds
umapplot(ds0)

f("TAGLN",ds0)

# ds0_markers <- FindAllMarkers(ds0,logfc.threshold = 0.7, min.diff.pct = 0.2)
selected_features <- AC_genes$Feature
temp <- get_data_table(ds0, highvar = F, type = "data")
ds0_data <- matrix(data=0, nrow = length(selected_features), 
                   ncol = length(colnames(temp)), byrow = FALSE, 
                   dimnames = list(selected_features,colnames(temp)))
for(i in intersect(selected_features,rownames(temp))){
  ds0_data[i,] <- temp[i,]
}
rm(temp)

ds0_label <- as.numeric(as.character(Idents(ds0)))
colnames(ds0_data) <- NULL
ds0_test_data <- list(data = t(as(ds0_data,"dgCMatrix")), label = ds0_label)
ds0_test <- xgb.DMatrix(data = ds0_test_data$data,label = ds0_test_data$label)

#计算混淆矩阵
predict_ds0_test <- round(predict(bst_model, newdata = ds0_test))

ds0_data_confuse_matrix_test <- table(ds0_test_data$label, predict_ds0_test, dnn=c("true","pre"))
ds0_data_confuse_matrix_test_prop <- prop.table(ds0_data_confuse_matrix_test,1)
x <- c("ds0_0", "ds0_1", "ds0_2", "ds0_3", "ds0_4")
y <- c("AC_0", "AC_1", "AC_2")

prop <- as.numeric(ds0_data_confuse_matrix_test_prop)
data <- expand.grid(x = x, y = y) %>% bind_cols(prop = prop)
plot <- ggplot(data, aes(x = x, y = y, colour = prop, size = prop)) +
  geom_point()+
  scale_size_continuous(range = c(0, 10)) + 
  labs(x = "clusters", y = "inferred from") + theme_bw()
ggsave("./plots/ACmodel_humancor.png", plot = plot, device = png, width = 5,height = 4)

ds0_data_confuse_matrix_test
    pre
true    0    1    2
   0 1997    2  179
   1   98  156 1531
   2    3 1208    2
   3  173    0    0
   4   52    0    0
ds0_data_confuse_matrix_test_prop  #分析发育轨迹
    pre
true            0            1            2
   0 0.9168962351 0.0009182736 0.0821854913
   1 0.0549019608 0.0873949580 0.8577030812
   2 0.0024732069 0.9958779885 0.0016488046
   3 1.0000000000 0.0000000000 0.0000000000
   4 1.0000000000 0.0000000000 0.0000000000
#ROC曲线
xgboost_roc <- pROC::multiclass.roc(ds0_test_data$label, predict_ds0_test) #多分类ROC
Setting direction: controls < cases
Setting direction: controls < cases
Setting direction: controls < cases
Setting direction: controls < cases
Setting direction: controls > cases
Setting direction: controls > cases
Setting direction: controls > cases
Setting direction: controls > cases
Setting direction: controls > cases
Setting direction: controls < cases
# 计算ARI 
adjustedRandIndex(predict_ds0_test, ds0_test_data$label)
[1] 0.7047121
labels <- lapply(levels(Idents(ds2_AC)), paste0, "_AC") %>% as.character()
labels2 <- lapply(levels(Idents(ds0)), paste0, "_ds0") %>% as.character()
sources <- rep(0:(length(labels)-1), each = length(labels2))  #注意这里的each和times的区别
colors <- rep(colors_list[1:length(labels)], each = length(labels2))
targets <- rep(length(labels)+0:(length(labels2)-1), times = length(labels))

plot_ly(type = "sankey", orientation = "h",
    node = list(
      label = c(labels,labels2), 
      color = colors_list, pad = 15, thickness = 30,
      line = list(
        color = "black",
        width = 1)),
    link = list(
      source = sources,
      target = targets,
      value =  as.numeric(ds0_data_confuse_matrix_test),
      color = colors
      ))
# load("./init.RData")
multi_featureplot(head(importance2,9)$Feature, ds2_AC)
multi_featureplot(head(importance2,9)$Feature, ds0)
multi_featureplot(head(importance2,9)$Feature, ds1)
f("MYH11", ds2_AC)
umapplot(ds0)

淋巴细胞

Idents(lym_ds2) <- lym_ds2$conditions
lym_ds2_AC <- subset(lym_ds2, idents = "AC")
lym_ds2_PA <- subset(lym_ds2, idents = "PA")
lym_ds2_AC <- lym_ds2_AC %>% FindNeighbors(dims = 1:20) %>% FindClusters(resolution = 0.2)
Computing nearest neighbor graph
Computing SNN
Modularity Optimizer version 1.3.0 by Ludo Waltman and Nees Jan van Eck

Number of nodes: 2990
Number of edges: 98189

Running Louvain algorithm...
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Maximum modularity in 10 random starts: 0.9128
Number of communities: 5
Elapsed time: 0 seconds
umapplot(lym_ds2_AC)

lym_ds2_PA <- lym_ds2_PA %>% FindNeighbors(dims = 1:20) %>% FindClusters(resolution = 0.2)
Computing nearest neighbor graph
Computing SNN
Modularity Optimizer version 1.3.0 by Ludo Waltman and Nees Jan van Eck

Number of nodes: 13746
Number of edges: 456548

Running Louvain algorithm...
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Maximum modularity in 10 random starts: 0.9116
Number of communities: 6
Elapsed time: 1 seconds
umapplot(lym_ds2_PA)

用PA的lym训练

lym_PA_data <- get_data_table(lym_ds2_PA, highvar = F, type = "data")
lym_PA_label <- as.numeric(as.character(Idents(lym_ds2_PA)))

set.seed(7)
index <- c(1:dim(lym_PA_data)[2]) %>% sample(ceiling(0.3*dim(lym_PA_data)[2]), replace = F, prob = NULL)
colnames(lym_PA_data) <- NULL
lym_PA_train_data <- list(data = t(as(lym_PA_data[,-index],"dgCMatrix")), label = lym_PA_label[-index])
lym_PA_test_data <- list(data = t(as(lym_PA_data[,index],"dgCMatrix")), label = lym_PA_label[index])

lym_PA_train <- xgb.DMatrix(data = lym_PA_train_data$data,label = lym_PA_train_data$label)
lym_PA_test <- xgb.DMatrix(data = lym_PA_test_data$data,label = lym_PA_test_data$label)

watchlist <- list(train = lym_PA_train, eval = lym_PA_test)
xgb_param <- list(eta = 0.2, max_depth = 6, 
                  subsample = 0.6,  num_class = length(table(Idents(lym_ds2_PA))),
                  objective = "multi:softmax", eval_metric = 'mlogloss')

bst_model <- xgb.train(xgb_param, lym_PA_train, nrounds = 200, watchlist, verbose = 1)
# 特征提取
importance <- xgb.importance(colnames(lym_PA_train), model = bst_model)
head(importance)
xgb.ggplot.importance(head(importance,20),n_clusters = 1)+theme_bw() #这个cluster和分类没有关系
lym_PA_genes <- head(importance, 500) ##选择top500

write.csv(lym_PA_genes,"./datatable/lym_PA_features.csv", row.names = F)
#混淆矩阵
predict_lym_PA_test <- round(predict(bst_model, newdata = lym_PA_test))

lym_PA_confuse_matrix_test <- table(lym_PA_test_data$label, predict_lym_PA_test, dnn=c("true","pre"))
lym_PA_confuse_matrix_test_prop <- prop.table(lym_PA_confuse_matrix_test, 1)
lym_PA_confuse_matrix_test_prop

x <- c("PA_lym_0", "PA_lym_1", "PA_lym_2", "PA_lym_3", "PA_lym_4", "PA_lym_5")
y <- c("PA_lym_0", "PA_lym_1", "PA_lym_2", "PA_lym_3", "PA_lym_4", "PA_lym_5")

prop <- as.numeric(lym_PA_confuse_matrix_test_prop)
data <- expand.grid(x = x, y = y) %>% bind_cols(prop = prop)
plot <- ggplot(data, aes(x = x, y = y, colour = prop, size = prop)) +
  geom_point()+
  scale_size_continuous(range = c(0, 10)) + 
  labs(x = "clusters", y = "inferred from") + theme_bw()
ggsave("./plots/PAlymmodel.png", plot = plot, device = png, width = 7,height =6)

用AC的lym验证

xgboost_roc[["auc"]]
Multi-class area under the curve: 0.7198
adjustedRandIndex(predict_lym_AC_test, lym_AC_test_data$label)
[1] 0.7227654
lym_AC_confuse_matrix_test_prop
    pre
true           0           1           2           3           4           5
   0 0.794649882 0.004720692 0.000000000 0.200629426 0.000000000 0.000000000
   1 0.000000000 0.030172414 0.897988506 0.000000000 0.071839080 0.000000000
   2 0.130268199 0.829501916 0.032567050 0.003831418 0.000000000 0.003831418
   3 0.000000000 0.041841004 0.000000000 0.000000000 0.958158996 0.000000000
   4 0.000000000 0.000000000 0.000000000 0.000000000 0.000000000 1.000000000
labels <- lapply(levels(Idents(lym_ds2_PA)), paste0, "_lymPA") %>% as.character()
labels2 <- lapply(levels(Idents(lym_ds2_AC)), paste0, "_lymAC") %>% as.character()
sources <- rep(0:5, each = 5)  #注意这里的each和times的区别
colors <- rep(colors_list[1:6], each = 5)
targets <- rep(6:10, times = 6)

plot_ly(type = "sankey", orientation = "h",
    node = list(
      label = c(labels,labels2), 
      color = colors_list, pad = 15, thickness = 30,
      line = list(
        color = "black",
        width = 1)),
    link = list(
      source = sources,
      target = targets,
      value =  as.numeric(lym_AC_confuse_matrix_test),
      color = colors
      ))



umapplot(lym_ds2_AC)


umapplot(lym_ds2_PA)

Add a new chunk by clicking the Insert Chunk button on the toolbar or by pressing Ctrl+Alt+I.

When you save the notebook, an HTML file containing the code and output will be saved alongside it (click the Preview button or press Ctrl+Shift+K to preview the HTML file).

The preview shows you a rendered HTML copy of the contents of the editor. Consequently, unlike Knit, Preview does not run any R code chunks. Instead, the output of the chunk when it was last run in the editor is displayed.

LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKVGhpcyBpcyBhbiBbUiBNYXJrZG93bl0oaHR0cDovL3JtYXJrZG93bi5yc3R1ZGlvLmNvbSkgTm90ZWJvb2suIFdoZW4geW91IGV4ZWN1dGUgY29kZSB3aXRoaW4gdGhlIG5vdGVib29rLCB0aGUgcmVzdWx0cyBhcHBlYXIgYmVuZWF0aCB0aGUgY29kZS4gCgpUcnkgZXhlY3V0aW5nIHRoaXMgY2h1bmsgYnkgY2xpY2tpbmcgdGhlICpSdW4qIGJ1dHRvbiB3aXRoaW4gdGhlIGNodW5rIG9yIGJ5IHBsYWNpbmcgeW91ciBjdXJzb3IgaW5zaWRlIGl0IGFuZCBwcmVzc2luZyAqQ3RybCtTaGlmdCtFbnRlciouIAoKYGBge3J9CmxpYnJhcnkoeGdib29zdCkKbGlicmFyeShNYXRyaXgpCmxpYnJhcnkobWNsdXN0KQpsaWJyYXJ5KHRpZHl2ZXJzZSkKYGBgCgpgYGB7cn0KZHMwIDwtIHJlYWRSRFMoIi4vZHMwLnJkcyIpCmRzMSA8LSByZWFkUkRTKCIuL2RzMS5yZHMiKQpkczIgPC0gcmVhZFJEUygiLi9kczIucmRzIikKYGBgCgojIOWIhuWPkeiuree7g+mbhgpgYGB7cn0KSWRlbnRzKGRzMikgPC0gZHMyJGNvbmRpdGlvbnMKZHMyX0FDIDwtIHN1YnNldChkczIsIGlkZW50cyA9ICJBQyIpCmRzMl9QQSA8LSBzdWJzZXQoZHMyLCBpZGVudHMgPSAiUEEiKQpkczJfQUMgPC0gZHMyX0FDICU+JSBGaW5kTmVpZ2hib3JzKGRpbXMgPSAxOjIwKSAlPiUgRmluZENsdXN0ZXJzKHJlc29sdXRpb24gPSAwLjEpCmRzMl9QQSA8LSBkczJfUEEgJT4lIEZpbmROZWlnaGJvcnMoZGltcyA9IDE6MjApICU+JSBGaW5kQ2x1c3RlcnMocmVzb2x1dGlvbiA9IDAuMSkKdW1hcHBsb3QoZHMyX0FDKSArIHNjYWxlX3lfY29udGludW91cyhsaW1pdHMgPSBjKC01LDE1KSxicmVha3MgPSBOVUxMKSArCiAgICAgICAgc2NhbGVfeF9jb250aW51b3VzKGxpbWl0cyA9IGMoLTUsMTUpLGJyZWFrcyA9IE5VTEwpCnVtYXBwbG90KGRzMl9QQSkrIHNjYWxlX3lfY29udGludW91cyhsaW1pdHMgPSBjKC01LDE1KSxicmVha3MgPSBOVUxMKSArCiAgICAgICAgc2NhbGVfeF9jb250aW51b3VzKGxpbWl0cyA9IGMoLTUsMTUpLGJyZWFrcyA9IE5VTEwpCgpBQ19tYXJrZXJzIDwtIEZpbmRBbGxNYXJrZXJzKGRzMl9BQyxsb2dmYy50aHJlc2hvbGQgPSAwLjcsIG1pbi5kaWZmLnBjdCA9IDAuMikKUEFfbWFya2VycyA8LSBGaW5kQWxsTWFya2VycyhkczJfUEEsbG9nZmMudGhyZXNob2xkID0gMC43LCBtaW4uZGlmZi5wY3QgPSAwLjIpCmBgYAoKIyMg5ZyoQUPkuIrpooTorq3nu4MKYGBge3J9CkFDX2RhdGEgPC0gZ2V0X2RhdGFfdGFibGUoZHMyX0FDLCBoaWdodmFyID0gRiwgdHlwZSA9ICJkYXRhIikKQUNfbGFiZWwgPC0gYXMubnVtZXJpYyhhcy5jaGFyYWN0ZXIoSWRlbnRzKGRzMl9BQykpKQoKc2V0LnNlZWQoNykKaW5kZXggPC0gYygxOmRpbShBQ19kYXRhKVsyXSkgJT4lIHNhbXBsZShjZWlsaW5nKDAuMypkaW0oQUNfZGF0YSlbMl0pLCByZXBsYWNlID0gRiwgcHJvYiA9IE5VTEwpCgpjb2xuYW1lcyhBQ19kYXRhKSA8LSBOVUxMCgpBQ190cmFpbl9kYXRhIDwtIGxpc3QoZGF0YSA9IHQoYXMoQUNfZGF0YVssLWluZGV4XSwiZGdDTWF0cml4IikpLCBsYWJlbCA9IEFDX2xhYmVsWy1pbmRleF0pCkFDX3Rlc3RfZGF0YSA8LSBsaXN0KGRhdGEgPSB0KGFzKEFDX2RhdGFbLGluZGV4XSwiZGdDTWF0cml4IikpLCBsYWJlbCA9IEFDX2xhYmVsW2luZGV4XSkKCkFDX3RyYWluIDwtIHhnYi5ETWF0cml4KGRhdGEgPSBBQ190cmFpbl9kYXRhJGRhdGEsbGFiZWwgPSBBQ190cmFpbl9kYXRhJGxhYmVsKQpBQ190ZXN0IDwtIHhnYi5ETWF0cml4KGRhdGEgPSBBQ190ZXN0X2RhdGEkZGF0YSxsYWJlbCA9IEFDX3Rlc3RfZGF0YSRsYWJlbCkKCiMgeGdiX3BhcmFtc190cmFpbiA9IHsKIyAgICAgJ29iamVjdGl2ZSc6J211bHRpOnNvZnRwcm9iJywKIyAgICAgJ2V2YWxfbWV0cmljJzonbWxvZ2xvc3MnLAojICAgICAnbnVtX2NsYXNzJzpzZWxmLm51bWJlcnRyYWluY2xhc3NlcywKIyAgICAgJ2V0YSc6MC4yLAojICAgICAnbWF4X2RlcHRoJzo2LAojICAgICAnc3Vic2FtcGxlJzogMC42fQojIG5yb3VuZCA9IDIwMAoKd2F0Y2hsaXN0IDwtIGxpc3QodHJhaW4gPSBBQ190cmFpbiwgZXZhbCA9IEFDX3Rlc3QpCnhnYl9wYXJhbSA8LSBsaXN0KGV0YSA9IDAuMiwgbWF4X2RlcHRoID0gNiwgCiAgICAgICAgICAgICAgICAgIHN1YnNhbXBsZSA9IDAuNiwgIG51bV9jbGFzcyA9IGxlbmd0aCh0YWJsZShJZGVudHMoZHMyX0FDKSkpLAogICAgICAgICAgICAgICAgICBvYmplY3RpdmUgPSAibXVsdGk6c29mdG1heCIsIGV2YWxfbWV0cmljID0gJ21sb2dsb3NzJykKCmJzdF9tb2RlbCA8LSB4Z2IudHJhaW4oeGdiX3BhcmFtLCBBQ190cmFpbiwgbnJvdW5kcyA9IDIwMCwgd2F0Y2hsaXN0LCB2ZXJib3NlID0gMCkKYGBgCgpgYGB7cixmaWcuaGVpZ2h0PTQsZmlnLndpZHRoPTR9CiMg54m55b6B5o+Q5Y+WCmltcG9ydGFuY2UgPC0geGdiLmltcG9ydGFuY2UoY29sbmFtZXMoQUNfdHJhaW4pLCBtb2RlbCA9IGJzdF9tb2RlbCkKaGVhZChpbXBvcnRhbmNlKQp4Z2IuZ2dwbG90LmltcG9ydGFuY2UoaGVhZChpbXBvcnRhbmNlLDIwKSxuX2NsdXN0ZXJzID0gMSkgKyB0aGVtZV9taW5pbWFsKCkKCm11bHRpX2ZlYXR1cmVwbG90KGhlYWQoaW1wb3J0YW5jZSw5KSRGZWF0dXJlLGRzMikgCkFDX2dlbmVzIDwtIGhlYWQoaW1wb3J0YW5jZSwgNTAwKSAjI+mAieaLqXRvcDUwMAoKd3JpdGUuY3N2KEFDX2dlbmVzLCAiLi9kYXRhdGFibGUvQUNfZmVhdHVyZXMuY3N2Iiwgcm93Lm5hbWVzID0gRikKCiPmt7fmt4bnn6npmLUKcHJlZGljdF9BQ190ZXN0IDwtIHJvdW5kKHByZWRpY3QoYnN0X21vZGVsLCBuZXdkYXRhID0gQUNfdGVzdCkpCgpBQ19jb25mdXNlX21hdHJpeF90ZXN0IDwtIHRhYmxlKEFDX3Rlc3RfZGF0YSRsYWJlbCwgcHJlZGljdF9BQ190ZXN0LCBkbm49YygidHJ1ZSIsInByZSIpKQpBQ19jb25mdXNlX21hdHJpeF90ZXN0X3Byb3AgPC0gcHJvcC50YWJsZShBQ19jb25mdXNlX21hdHJpeF90ZXN0LCAxKQpBQ19jb25mdXNlX21hdHJpeF90ZXN0X3Byb3AKCnggPC0gYygiQUNfMCIsICJBQ18xIiwgIkFDXzIiLCAiQUNfMyIpCnkgPC0gYygiQUNfMCIsICJBQ18xIiwgIkFDXzIiLCAiQUNfMyIpCnByb3AgPC0gYXMubnVtZXJpYyhBQ19jb25mdXNlX21hdHJpeF90ZXN0X3Byb3ApCmRhdGEgPC0gZXhwYW5kLmdyaWQoeCA9IHgsIHkgPSB5KSAlPiUgYmluZF9jb2xzKHByb3AgPSBwcm9wKQpwbG90IDwtIGdncGxvdChkYXRhLCBhZXMoeCA9IHgsIHkgPSB5LCBjb2xvdXIgPSBwcm9wLCBzaXplID0gcHJvcCkpICsKICBnZW9tX3BvaW50KCkrCiAgc2NhbGVfc2l6ZV9jb250aW51b3VzKHJhbmdlID0gYygwLCAxMCkpICsgIyBBZGp1c3QgYXMgcmVxdWlyZWQuCiAgbGFicyh4ID0gTlVMTCwgeSA9IE5VTEwpICsgdGhlbWVfYncoKQpnZ3NhdmUoIi4vcGxvdHMvQUNfcHJldHJhaW4ucG5nIiwgcGxvdCA9IHBsb3QsIGRldmljZSA9IHBuZyx3aWR0aCA9IDUsaGVpZ2h0ID0gNCkKCiNST0Pmm7Lnur8KeGdib29zdF9yb2MgPC0gcFJPQzo6bXVsdGljbGFzcy5yb2MoQUNfdGVzdF9kYXRhJGxhYmVsLCBwcmVkaWN0X0FDX3Rlc3QpICPlpJrliIbnsbtST0MKeGdib29zdF9yb2NbWyJhdWMiXV0gI+WPqumcgOimgei/meS4quWAvAphZGp1c3RlZFJhbmRJbmRleChBQ190ZXN0X2RhdGEkbGFiZWwsIHByZWRpY3RfQUNfdGVzdCkgI+WIhuexu+WZqOaAp+iDvQpgYGAKCgojIyDlnKhQQeS4iuiuree7gwpgYGB7cn0KUEFfZGF0YSA8LSBnZXRfZGF0YV90YWJsZShkczJfUEEsIGhpZ2h2YXIgPSBGLCB0eXBlID0gImRhdGEiKQpQQV9sYWJlbCA8LSBhcy5udW1lcmljKGFzLmNoYXJhY3RlcihJZGVudHMoZHMyX1BBKSkpCnNldC5zZWVkKDcpCmluZGV4IDwtIGMoMTpkaW0oUEFfZGF0YSlbMl0pICU+JSBzYW1wbGUoY2VpbGluZygwLjMqZGltKFBBX2RhdGEpWzJdKSwgcmVwbGFjZSA9IEYsIHByb2IgPSBOVUxMKQpjb2xuYW1lcyhQQV9kYXRhKSA8LSBOVUxMCgpQQV90cmFpbl9kYXRhIDwtIGxpc3QoZGF0YSA9IHQoYXMoUEFfZGF0YVssLWluZGV4XSwiZGdDTWF0cml4IikpLCBsYWJlbCA9IFBBX2xhYmVsWy1pbmRleF0pClBBX3Rlc3RfZGF0YSA8LSBsaXN0KGRhdGEgPSB0KGFzKFBBX2RhdGFbLGluZGV4XSwiZGdDTWF0cml4IikpLCBsYWJlbCA9IFBBX2xhYmVsW2luZGV4XSkKClBBX3RyYWluIDwtIHhnYi5ETWF0cml4KGRhdGEgPSBQQV90cmFpbl9kYXRhJGRhdGEsbGFiZWwgPSBQQV90cmFpbl9kYXRhJGxhYmVsKQpQQV90ZXN0IDwtIHhnYi5ETWF0cml4KGRhdGEgPSBQQV90ZXN0X2RhdGEkZGF0YSxsYWJlbCA9IFBBX3Rlc3RfZGF0YSRsYWJlbCkKCndhdGNobGlzdCA8LSBsaXN0KHRyYWluID0gUEFfdHJhaW4sIGV2YWwgPSBQQV90ZXN0KQp4Z2JfcGFyYW0gPC0gbGlzdChldGEgPSAwLjIsIG1heF9kZXB0aCA9IDYsIAogICAgICAgICAgICAgICAgICBzdWJzYW1wbGUgPSAwLjYsICBudW1fY2xhc3MgPSBsZW5ndGgodGFibGUoSWRlbnRzKGRzMl9QQSkpKSwKICAgICAgICAgICAgICAgICAgb2JqZWN0aXZlID0gIm11bHRpOnNvZnRtYXgiLCBldmFsX21ldHJpYyA9ICdtbG9nbG9zcycpCmJzdF9tb2RlbCA8LSB4Z2IudHJhaW4oeGdiX3BhcmFtLCBQQV90cmFpbiwgbnJvdW5kcyA9IDIwMCwgd2F0Y2hsaXN0LCB2ZXJib3NlID0gMCkKYGBgCgpgYGB7cixmaWcuaGVpZ2h0PTQsZmlnLndpZHRoPTR9CiMg54m55b6B5o+Q5Y+WCmltcG9ydGFuY2UgPC0geGdiLmltcG9ydGFuY2UoY29sbmFtZXMoUEFfdHJhaW4pLCBtb2RlbCA9IGJzdF9tb2RlbCkKaGVhZChpbXBvcnRhbmNlKQp4Z2IuZ2dwbG90LmltcG9ydGFuY2UoaGVhZChpbXBvcnRhbmNlLDIwKSxuX2NsdXN0ZXJzID0gMSkgKyB0aGVtZV9taW5pbWFsKCkKCm11bHRpX2ZlYXR1cmVwbG90KGhlYWQoaW1wb3J0YW5jZSw5KSRGZWF0dXJlLCBkczIpClBBX2dlbmVzIDwtIGhlYWQoaW1wb3J0YW5jZSwgNTAwKSAjI+mAieaLqXRvcDUwMAp3cml0ZS5jc3YoUEFfZ2VuZXMsICIuL2RhdGF0YWJsZS9QQV9mZWF0dXJlcy5jc3YiLCByb3cubmFtZXMgPSBGKQoKI+a3t+a3huefqemYtQpwcmVkaWN0X1BBX3Rlc3QgPC0gcm91bmQocHJlZGljdChic3RfbW9kZWwsIG5ld2RhdGEgPSBQQV90ZXN0KSkKClBBX2NvbmZ1c2VfbWF0cml4X3Rlc3QgPC0gdGFibGUoUEFfdGVzdF9kYXRhJGxhYmVsLCBwcmVkaWN0X1BBX3Rlc3QsIGRubj1jKCJ0cnVlIiwicHJlIikpClBBX2NvbmZ1c2VfbWF0cml4X3Rlc3RfcHJvcCA8LSBwcm9wLnRhYmxlKFBBX2NvbmZ1c2VfbWF0cml4X3Rlc3QsMSkKUEFfY29uZnVzZV9tYXRyaXhfdGVzdF9wcm9wCgp4IDwtIGMoIlBBXzAiLCAiUEFfMSIsICJQQV8yIikKeSA8LSBjKCJQQV8wIiwgIlBBXzEiLCAiUEFfMiIpCnByb3AgPC0gYXMubnVtZXJpYyhQQV9jb25mdXNlX21hdHJpeF90ZXN0X3Byb3ApCmRhdGEgPC0gZXhwYW5kLmdyaWQoeCA9IHgsIHkgPSB5KSAlPiUgYmluZF9jb2xzKHByb3AgPSBwcm9wKQpwbG90IDwtIGdncGxvdChkYXRhLCBhZXMoeCA9IHgsIHkgPSB5LCBjb2xvdXIgPSBwcm9wLCBzaXplID0gcHJvcCkpICsKICBnZW9tX3BvaW50KCkrCiAgc2NhbGVfc2l6ZV9jb250aW51b3VzKHJhbmdlID0gYygwLCAxMCkpICsgIyBBZGp1c3QgYXMgcmVxdWlyZWQuCiAgbGFicyh4ID0gTlVMTCwgeSA9IE5VTEwpICsgdGhlbWVfYncoKQpnZ3NhdmUoIi4vcGxvdHMvUEFfcHJldHJhaW4ucG5nIiwgcGxvdCA9IHBsb3QsIGRldmljZSA9IHBuZyx3aWR0aCA9IDUsaGVpZ2h0ID0gNCkKCiNST0Pmm7Lnur8KCnhnYm9vc3Rfcm9jIDwtIHBST0M6Om11bHRpY2xhc3Mucm9jKFBBX3Rlc3RfZGF0YSRsYWJlbCwgcHJlZGljdF9QQV90ZXN0KSAj5aSa5YiG57G7Uk9DCnhnYm9vc3Rfcm9jW1siYXVjIl1dCmFkanVzdGVkUmFuZEluZGV4KFBBX3Rlc3RfZGF0YSRsYWJlbCwgcHJlZGljdF9QQV90ZXN0KSAjUEHliIbnsbvlmajmgKfog70KYGBgCiMjIOmAieaLqeeJueW+gWNvbW1vbiBnZW5lcyBvZiB0b3AgNTAwCiMjIOS9v+eUqOaJgOacieadpeiHqlBB55qE57uG6IOe6K6t57uD5YiG57G75ZmoCiMjIOW6lOeUqOWcqEFD5LiK77yM6K6h566XQVJJCmBgYHtyLGZpZy5oZWlnaHQ9NCxmaWcud2lkdGg9NH0Kc2VsZWN0ZWRfZmVhdHVyZXMgPC0gaW50ZXJzZWN0KFBBX2dlbmVzJEZlYXR1cmUsIEFDX2dlbmVzJEZlYXR1cmUpCndyaXRlLmNzdihzZWxlY3RlZF9mZWF0dXJlcywgIi4vZGF0YXRhYmxlL3NlbGVjdGVkX2ZlYXR1cmVzLmNzdiIsIHJvdy5uYW1lcyA9IEYpCgpQQV9kYXRhIDwtIGdldF9kYXRhX3RhYmxlKGRzMl9QQSwgaGlnaHZhciA9IEYsIHR5cGUgPSAiZGF0YSIpClBBX2RhdGEgPC0gUEFfZGF0YVtzZWxlY3RlZF9mZWF0dXJlcyxdClBBX2xhYmVsIDwtIGFzLm51bWVyaWMoYXMuY2hhcmFjdGVyKElkZW50cyhkczJfUEEpKSkKY29sbmFtZXMoUEFfZGF0YSkgPC0gTlVMTAoKUEFfdHJhaW5fZGF0YSA8LSBsaXN0KGRhdGEgPSB0KGFzKFBBX2RhdGEsImRnQ01hdHJpeCIpKSwgbGFiZWwgPSBQQV9sYWJlbCkKUEFfdHJhaW4gPC0geGdiLkRNYXRyaXgoZGF0YSA9IFBBX3RyYWluX2RhdGEkZGF0YSxsYWJlbCA9IFBBX3RyYWluX2RhdGEkbGFiZWwpCnhnYl9wYXJhbSA8LSBsaXN0KGV0YSA9IDAuMiwgbWF4X2RlcHRoID0gNiwgCiAgICAgICAgICAgICAgICAgIHN1YnNhbXBsZSA9IDAuNiwgIG51bV9jbGFzcyA9IGxlbmd0aCh0YWJsZShJZGVudHMoZHMyX1BBKSkpLAogICAgICAgICAgICAgICAgICBvYmplY3RpdmUgPSAibXVsdGk6c29mdG1heCIsIGV2YWxfbWV0cmljID0gJ21sb2dsb3NzJykKCmJzdF9tb2RlbCA8LSB4Z2IudHJhaW4oeGdiX3BhcmFtLCBQQV90cmFpbiwgbnJvdW5kcyA9IDIwMCwgdmVyYm9zZSA9IDEpCgojIOeJueW+geaPkOWPlgppbXBvcnRhbmNlIDwtIHhnYi5pbXBvcnRhbmNlKGNvbG5hbWVzKFBBX3RyYWluKSwgbW9kZWwgPSBic3RfbW9kZWwpCmhlYWQoaW1wb3J0YW5jZSkKeGdiLmdncGxvdC5pbXBvcnRhbmNlKGhlYWQoaW1wb3J0YW5jZSwyMCksbl9jbHVzdGVycyA9IDEpICsgdGhlbWVfYncoKQp3cml0ZS5jc3YoaW1wb3J0YW5jZSwgIi4vZGF0YXRhYmxlL1BBdHJhaW5fZmVhdHVyZXMuY3N2Iiwgcm93Lm5hbWVzID0gRikKCiMgbXVsdGlfZmVhdHVyZXBsb3QoaGVhZChpbXBvcnRhbmNlLDkpJEZlYXR1cmUsIGRzMikKCmBgYAojIyDlupTnlKjliLBBQ+S4igpgYGB7cn0KQUNfZGF0YSA8LSBnZXRfZGF0YV90YWJsZShkczJfQUMsIGhpZ2h2YXIgPSBGLCB0eXBlID0gImRhdGEiKQpBQ19kYXRhIDwtIEFDX2RhdGFbc2VsZWN0ZWRfZmVhdHVyZXMsXQpBQ19sYWJlbCA8LSBhcy5udW1lcmljKGFzLmNoYXJhY3RlcihJZGVudHMoZHMyX0FDKSkpCmNvbG5hbWVzKEFDX2RhdGEpIDwtIE5VTEwKQUNfdGVzdF9kYXRhIDwtIGxpc3QoZGF0YSA9IHQoYXMoQUNfZGF0YSwiZGdDTWF0cml4IikpLCBsYWJlbCA9IEFDX2xhYmVsKQpBQ190ZXN0IDwtIHhnYi5ETWF0cml4KGRhdGEgPSBBQ190ZXN0X2RhdGEkZGF0YSxsYWJlbCA9IEFDX3Rlc3RfZGF0YSRsYWJlbCkKCiPorqHnrpfmt7fmt4bnn6npmLUKcHJlZGljdF9BQ190ZXN0IDwtIHJvdW5kKHByZWRpY3QoYnN0X21vZGVsLCBuZXdkYXRhID0gQUNfdGVzdCkpCgpBQ19jb25mdXNlX21hdHJpeF90ZXN0IDwtIHRhYmxlKEFDX3Rlc3RfZGF0YSRsYWJlbCwgcHJlZGljdF9BQ190ZXN0LCBkbm49YygidHJ1ZSIsInByZSIpKQpBQ19jb25mdXNlX21hdHJpeF90ZXN0X3Byb3AgPC0gcHJvcC50YWJsZShBQ19jb25mdXNlX21hdHJpeF90ZXN0LDEpCkFDX2NvbmZ1c2VfbWF0cml4X3Rlc3RfcHJvcCAgI+WIhuaekOWPkeiCsui9qOi/uQoKeCA8LSBjKCJBQ18wIiwgIkFDXzEiLCAiQUNfMiIsICJBQ18zIikKeSA8LSBjKCJQQV8wIiwgIlBBXzEiLCAiUEFfMiIpCnByb3AgPC0gYXMubnVtZXJpYyhBQ19jb25mdXNlX21hdHJpeF90ZXN0X3Byb3ApCmRhdGEgPC0gZXhwYW5kLmdyaWQoeCA9IHgsIHkgPSB5KSAlPiUgYmluZF9jb2xzKHByb3AgPSBwcm9wKQpwbG90IDwtIGdncGxvdChkYXRhLCBhZXMoeCA9IHgsIHkgPSB5LCBjb2xvdXIgPSBwcm9wLCBzaXplID0gcHJvcCkpICsKICBnZW9tX3BvaW50KCkrCiAgc2NhbGVfc2l6ZV9jb250aW51b3VzKHJhbmdlID0gYygwLCAxMCkpICsgCiAgbGFicyh4ID0gImNsdXN0ZXJzIiwgeSA9ICJpbmZlcnJlZCBmcm9tIikgKyB0aGVtZV9idygpCgpnZ3NhdmUoIi4vcGxvdHMvUEF0b0FDX3RyYWluLnBuZyIsIHBsb3QgPSBwbG90LCBkZXZpY2UgPSBwbmcsIHdpZHRoID0gNSxoZWlnaHQgPSA0KQoKI1JPQ+absue6vwp4Z2Jvb3N0X3JvYyA8LSBwUk9DOjptdWx0aWNsYXNzLnJvYyhBQ190ZXN0X2RhdGEkbGFiZWwsIHByZWRpY3RfQUNfdGVzdCkgI+WkmuWIhuexu1JPQwp4Z2Jvb3N0X3JvY1tbImF1YyJdXQoKIyBtdWx0aV9mZWF0dXJlcGxvdChoZWFkKGltcG9ydGFuY2UsNCkkRmVhdHVyZSwgZHMyLCBzcGxpdC5ieSA9ICJjb25kaXRpb25zIikKIyBtdWx0aV9mZWF0dXJlcGxvdChoZWFkKGltcG9ydGFuY2UsOSkkRmVhdHVyZSwgZHMyX0FDKQoKIyDorqHnrpdBUkkgCmFkanVzdGVkUmFuZEluZGV4KHByZWRpY3RfQUNfdGVzdCwgQUNfdGVzdF9kYXRhJGxhYmVsKQpgYGAKCjwhLS0gIyDpgInmi6nnibnlvoFjb21tb24gZ2VuZXMgb2YgdG9wIDUwMCAtLT4KPCEtLSAjIyDkvb/nlKjmiYDmnInmnaXoh6pBQ+eahOe7huiDnuiuree7g+WIhuexu+WZqCAtLT4KCjwhLS0gYGBge3J9IC0tPgo8IS0tIEFDX2RhdGEgPC0gZ2V0X2RhdGFfdGFibGUoZHMyX0FDLCBoaWdodmFyID0gRiwgdHlwZSA9ICJkYXRhIikgLS0+CjwhLS0gQUNfZGF0YSA8LSBBQ19kYXRhW3NlbGVjdGVkX2ZlYXR1cmVzLF0gLS0+CjwhLS0gQUNfbGFiZWwgPC0gYXMubnVtZXJpYyhhcy5jaGFyYWN0ZXIoSWRlbnRzKGRzMl9BQykpKSAtLT4KPCEtLSBjb2xuYW1lcyhBQ19kYXRhKSA8LSBOVUxMIC0tPgoKPCEtLSBBQ190cmFpbl9kYXRhIDwtIGxpc3QoZGF0YSA9IHQoYXMoQUNfZGF0YSwiZGdDTWF0cml4IikpLCBsYWJlbCA9IEFDX2xhYmVsKSAtLT4KCjwhLS0gQUNfdHJhaW4gPC0geGdiLkRNYXRyaXgoZGF0YSA9IEFDX3RyYWluX2RhdGEkZGF0YSxsYWJlbCA9IEFDX3RyYWluX2RhdGEkbGFiZWwpIC0tPgoKPCEtLSB4Z2JfQUNyYW0gPC0gbGlzdChldGEgPSAwLjIsIG1heF9kZXB0aCA9IDYsICAtLT4KPCEtLSAgICAgICAgICAgICAgICAgICBzdWJzYW1wbGUgPSAwLjYsICBudW1fY2xhc3MgPSBsZW5ndGgodGFibGUoSWRlbnRzKGRzMl9BQykpKSwgLS0+CjwhLS0gICAgICAgICAgICAgICAgICAgb2JqZWN0aXZlID0gIm11bHRpOnNvZnRtYXgiLCBldmFsX21ldHJpYyA9ICdtbG9nbG9zcycpIC0tPgoKPCEtLSBic3RfbW9kZWwyIDwtIHhnYi50cmFpbih4Z2JfQUNyYW0sIEFDX3RyYWluLCBucm91bmRzID0gMjAwLCB2ZXJib3NlID0gMSkgLS0+Cgo8IS0tICMg54m55b6B5o+Q5Y+WIC0tPgo8IS0tIGltcG9ydGFuY2UyIDwtIHhnYi5pbXBvcnRhbmNlKGNvbG5hbWVzKEFDX3RyYWluKSwgbW9kZWwgPSBic3RfbW9kZWwyKSAtLT4KPCEtLSBoZWFkKGltcG9ydGFuY2UyKSAtLT4KPCEtLSB4Z2IuZ2dwbG90LmltcG9ydGFuY2UoaGVhZChpbXBvcnRhbmNlMiwyMCksbl9jbHVzdGVycyA9IDEpIC0tPgoKPCEtLSBtdWx0aV9mZWF0dXJlcGxvdChoZWFkKGltcG9ydGFuY2UyLDkpJEZlYXR1cmUsIGRzMikgLS0+CjwhLS0gYGBgIC0tPgoKCjwhLS0gIyMg5bqU55So5ZyoUEHkuIrvvIzorqHnrpdBUkkgLS0+CjwhLS0gYGBge3J9IC0tPgo8IS0tIFBBX2RhdGEgPC0gZ2V0X2RhdGFfdGFibGUoZHMyX1BBLCBoaWdodmFyID0gRiwgdHlwZSA9ICJkYXRhIikgLS0+CjwhLS0gUEFfZGF0YSA8LSBQQV9kYXRhW3NlbGVjdGVkX2ZlYXR1cmVzLF0gLS0+CjwhLS0gUEFfbGFiZWwgPC0gYXMubnVtZXJpYyhhcy5jaGFyYWN0ZXIoSWRlbnRzKGRzMl9QQSkpKSAtLT4KPCEtLSBjb2xuYW1lcyhQQV9kYXRhKSA8LSBOVUxMIC0tPgoKPCEtLSBQQV90ZXN0X2RhdGEgPC0gbGlzdChkYXRhID0gdChhcyhQQV9kYXRhLCJkZ0NNYXRyaXgiKSksIGxhYmVsID0gUEFfbGFiZWwpIC0tPgoKPCEtLSBQQV90ZXN0IDwtIHhnYi5ETWF0cml4KGRhdGEgPSBQQV90ZXN0X2RhdGEkZGF0YSxsYWJlbCA9IFBBX3Rlc3RfZGF0YSRsYWJlbCkgLS0+Cgo8IS0tICPorqHnrpfmt7fmt4bnn6npmLUgLS0+CjwhLS0gcHJlZGljdF9QQV90ZXN0IDwtIHJvdW5kKHByZWRpY3QoYnN0X21vZGVsMiwgbmV3ZGF0YSA9IFBBX3Rlc3QpKSAtLT4KCjwhLS0gUEFfY29uZnVzZV9tYXRyaXhfdGVzdCA8LSB0YWJsZShQQV90ZXN0X2RhdGEkbGFiZWwsIHByZWRpY3RfUEFfdGVzdCwgZG5uPWMoInRydWUiLCJwcmUiKSkgLS0+CjwhLS0gUEFfY29uZnVzZV9tYXRyaXhfdGVzdF9wcm9wIDwtIHByb3AudGFibGUoUEFfY29uZnVzZV9tYXRyaXhfdGVzdCwxKSAtLT4KPCEtLSBQQV9jb25mdXNlX21hdHJpeF90ZXN0X3Byb3AgICPliIbmnpDlj5HogrLovajov7kgLS0+Cgo8IS0tICNST0Pmm7Lnur8gLS0+CjwhLS0gIyB4Z2Jvb3N0X3JvYyA8LSBwUk9DOjptdWx0aWNsYXNzLnJvYyhQQV90ZXN0X2RhdGEkbGFiZWwsIHByZWRpY3RfUEFfdGVzdCkgI+WkmuWIhuexu1JPQyAtLT4KPCEtLSB4Z2Jvb3N0X3JvYyA8LSBwUk9DOjpyb2MoUEFfdGVzdF9kYXRhJGxhYmVsLCBwcmVkaWN0X1BBX3Rlc3QpIC0tPgo8IS0tIHBsb3QoeGdib29zdF9yb2MsIHByaW50LmF1Yz1UUlVFLCBhdWMucG9seWdvbj1UUlVFLCAgLS0+CjwhLS0gICBncmlkPWMoMC4xLCAwLjIpLGdyaWQuY29sPWMoImdyZWVuIiwgInJlZCIpLCAgLS0+CjwhLS0gICBtYXguYXVjLnBvbHlnb249VFJVRSxhdWMucG9seWdvbi5jb2w9InNreWJsdWUiLCAgLS0+CjwhLS0gICBwcmludC50aHJlcz1UUlVFLG1haW49J1JPQyBjdXJ2ZScpICPliY3kuKTkuKrliIbph49ST0MgLS0+Cgo8IS0tICMg6K6h566XQVJJICAtLT4KCjwhLS0gYWRqdXN0ZWRSYW5kSW5kZXgocHJlZGljdF9QQV90ZXN0LCBQQV90ZXN0X2RhdGEkbGFiZWwpIC0tPgo8IS0tIGBgYCAtLT4KCgoKYGBge3J9CnVtYXBwbG90KGRzMixzcGxpdC5ieSA9ICJjb25kaXRpb25zIikKdGFibGUoZHMyJGNvbmRpdGlvbnMpCmBgYAoKCiMgc2Fua2V5IHBsb3QKCiMjIFBBIC0+IEFDICAgICBBUkkgMC4zMDI0ODM3CmBgYAogICAgcHJlCnRydWUgICAgICAgICAgIDAgICAgICAgICAgIDEgICAgICAgICAgIDIKICAgMCAwLjk4MDM2MDA2NSAwLjAwMzI3MzMyMiAwLjAxNjM2NjYxMgogICAxIDAuNzk5NTE2OTA4IDAuMTk2ODU5OTAzIDAuMDAzNjIzMTg4CiAgIDIgMC40NTMwMDQ2MjIgMC40OTMwNjYyNTYgMC4wNTM5MjkxMjIKICAgMyAwLjAwMjc2MjQzMSAwLjA1MjQ4NjE4OCAwLjk0NDc1MTM4MQpgYGAKIyMgQUMgLT4gUEEgICAgQVJJIDAuMTc5NzY4OQpgYGAKICAgIHByZQp0cnVlICAgICAgICAgICAwICAgICAgICAgICAxICAgICAgICAgICAyICAgICAgICAgICAzCiAgIDAgMC4wMjcxMDc0MzggMC4yODcyNzI3MjcgMC42ODI2NDQ2MjggMC4wMDI5NzUyMDcKICAgMSAwLjAwMDM0OTg5NSAwLjA3NTIyNzQzMiAwLjkxNDk3NTUwNyAwLjAwOTQ0NzE2NgogICAyIDAuMDA4MTMwMDgxIDAuMDAzMjUyMDMzIDAuMTc1NjA5NzU2IDAuODEzMDA4MTMwCmBgYApgYGB7cn0KbGlicmFyeShwbG90bHkpCgpwbG90X2x5KHR5cGUgPSAic2Fua2V5Iiwgb3JpZW50YXRpb24gPSAiaCIsCiAgICBub2RlID0gbGlzdCgKICAgICAgbGFiZWwgPSBjKCJQQV8wIiwgIlBBXzEiLCAiUEFfMiIsICJBQ18wIiwiQUNfMSIsIkFDXzIiLCJBQ18zIiksIAogICAgICBjb2xvciA9IGNvbG9yc19saXN0LCBwYWQgPSAxNSwgdGhpY2tuZXNzID0gMzAsCiAgICAgIGxpbmUgPSBsaXN0KAogICAgICAgIGNvbG9yID0gImJsYWNrIiwKICAgICAgICB3aWR0aCA9IDEpKSwKICAgIGxpbmsgPSBsaXN0KAogICAgICBzb3VyY2UgPSBjKDAsMCwwLDAsMSwxLDEsMSwyLDIsMiwyKSwKICAgICAgdGFyZ2V0ID0gYygzLDQsNSw2LDMsNCw1LDYsMyw0LDUsNiksCiAgICAgIHZhbHVlID0gIGFzLm51bWVyaWMoQUNfY29uZnVzZV9tYXRyaXhfdGVzdCksCiAgICAgIGNvbG9yID0gYygiIzY2QzJBNSIsIiM2NkMyQTUiLCIjNjZDMkE1IiwiIzY2QzJBNSIsIiNGQzhENjIiLCIjRkM4RDYyIiwiI0ZDOEQ2MiIsCiAgICAgICAgICAgICAgICAiI0ZDOEQ2MiIsIiM4REEwQ0IiLCIjOERBMENCIiwiIzhEQTBDQiIsIiM4REEwQ0IiKQogICAgICApKSU+JSBsYXlvdXQoCiAgICB0aXRsZSA9ICJQQSAtPiBBQyIsCiAgICBmb250ID0gbGlzdChzaXplID0gMTAgKSkKCnVtYXBwbG90KGRzMl9BQykKdW1hcHBsb3QoZHMyX1BBKQp1bWFwcGxvdChkczIsc3BsaXQuYnkgPSAiY29uZGl0aW9ucyIpCmBgYAoKIyB2YXJpZnkg6YOo5YiGCueXheWPmOeoi+W6pumHj+WMlgojIOaVsOaNrumbhkNBX2RhdGFzZXQxCiMjIOWcqGRhdGFzZXQy55qEQUPkuIrorq3nu4MgIOS9v+eUqHRvcDUwMCBpbiBBQywgNzoz5YiG5Y+R6K6t57uD6ZuGCmBgYHtyfQp0ZW1wIDwtIGdldF9kYXRhX3RhYmxlKGRzMl9BQywgaGlnaHZhciA9IEYsIHR5cGUgPSAiZGF0YSIpCkFDX2RhdGEgPC0gbWF0cml4KGRhdGE9MCwgbnJvdyA9IGxlbmd0aChBQ19nZW5lcyRGZWF0dXJlKSwgbmNvbCA9IGxlbmd0aChjb2xuYW1lcyh0ZW1wKSksIGJ5cm93ID0gRkFMU0UsIGRpbW5hbWVzID0gbGlzdChBQ19nZW5lcyRGZWF0dXJlLGNvbG5hbWVzKHRlbXApKSkKCmZvcihpIGluIGludGVyc2VjdChBQ19nZW5lcyRGZWF0dXJlLCByb3duYW1lcyh0ZW1wKSkpewogIEFDX2RhdGFbaSxdIDwtIHRlbXBbaSxdCn0Kcm0odGVtcCkKQUNfbGFiZWwgPC0gYXMubnVtZXJpYyhhcy5jaGFyYWN0ZXIoSWRlbnRzKGRzMl9BQykpKQpzZXQuc2VlZCg3KQppbmRleCA8LSBjKDE6ZGltKEFDX2RhdGEpWzJdKSAlPiUgc2FtcGxlKGNlaWxpbmcoMC4zKmRpbShBQ19kYXRhKVsyXSksIHJlcGxhY2UgPSBGLCBwcm9iID0gTlVMTCkKY29sbmFtZXMoQUNfZGF0YSkgPC0gTlVMTAoKQUNfdHJhaW5fZGF0YSA8LSBsaXN0KGRhdGEgPSB0KGFzKEFDX2RhdGFbLC1pbmRleF0sImRnQ01hdHJpeCIpKSwgbGFiZWwgPSBBQ19sYWJlbFstaW5kZXhdKQpBQ190ZXN0X2RhdGEgPC0gbGlzdChkYXRhID0gdChhcyhBQ19kYXRhWyxpbmRleF0sImRnQ01hdHJpeCIpKSwgbGFiZWwgPSBBQ19sYWJlbFtpbmRleF0pCgpBQ190cmFpbiA8LSB4Z2IuRE1hdHJpeChkYXRhID0gQUNfdHJhaW5fZGF0YSRkYXRhLGxhYmVsID0gQUNfdHJhaW5fZGF0YSRsYWJlbCkKQUNfdGVzdCA8LSB4Z2IuRE1hdHJpeChkYXRhID0gQUNfdGVzdF9kYXRhJGRhdGEsbGFiZWwgPSBBQ190ZXN0X2RhdGEkbGFiZWwpCgp3YXRjaGxpc3QgPC0gbGlzdCh0cmFpbiA9IEFDX3RyYWluLCBldmFsID0gQUNfdGVzdCkKeGdiX3BhcmFtIDwtIGxpc3QoZXRhID0gMC4yLCBtYXhfZGVwdGggPSA2LCAKICAgICAgICAgICAgICAgICAgc3Vic2FtcGxlID0gMC42LCAgbnVtX2NsYXNzID0gbGVuZ3RoKHRhYmxlKElkZW50cyhkczJfQUMpKSksCiAgICAgICAgICAgICAgICAgIG9iamVjdGl2ZSA9ICJtdWx0aTpzb2Z0bWF4IiwgZXZhbF9tZXRyaWMgPSAnbWxvZ2xvc3MnKQoKYnN0X21vZGVsIDwtIHhnYi50cmFpbih4Z2JfcGFyYW0sIEFDX3RyYWluLCBucm91bmRzID0gMjAwLCB3YXRjaGxpc3QsIHZlcmJvc2UgPSAwKQpgYGAKCmBgYHtyLGZpZy53aWR0aD00LGZpZy5oZWlnaHQ9NH0KIyDnibnlvoHmj5Dlj5YKaW1wb3J0YW5jZSA8LSB4Z2IuaW1wb3J0YW5jZShjb2xuYW1lcyhBQ190cmFpbiksIG1vZGVsID0gYnN0X21vZGVsKQpoZWFkKGltcG9ydGFuY2UpCnhnYi5nZ3Bsb3QuaW1wb3J0YW5jZShoZWFkKGltcG9ydGFuY2UsMjApLG5fY2x1c3RlcnMgPSAxKSArIHRoZW1lX2J3KCkj6L+Z5LiqY2x1c3RlcuWSjOWIhuexu+ayoeacieWFs+ezuwp3cml0ZS5jc3YoaW1wb3J0YW5jZSwgIi4vZGF0YXRhYmxlL0FDbW9kZWxfZmVhdHVyZXMuY3N2Iiwgcm93Lm5hbWVzID0gRikKCiPmt7fmt4bnn6npmLUKcHJlZGljdF9BQ190ZXN0IDwtIHJvdW5kKHByZWRpY3QoYnN0X21vZGVsLCBuZXdkYXRhID0gQUNfdGVzdCkpCgpBQ19jb25mdXNlX21hdHJpeF90ZXN0IDwtIHRhYmxlKEFDX3Rlc3RfZGF0YSRsYWJlbCwgcHJlZGljdF9BQ190ZXN0LCBkbm49YygidHJ1ZSIsInByZSIpKQpBQ19jb25mdXNlX21hdHJpeF90ZXN0X3Byb3AgPC0gcHJvcC50YWJsZShBQ19jb25mdXNlX21hdHJpeF90ZXN0LCAxKQoKeCA8LSBjKCJBQ18wIiwgIkFDXzEiLCAiQUNfMiIsICJBQ18zIikKeSA8LSBjKCJBQ18wIiwgIkFDXzEiLCAiQUNfMiIsICJBQ18zIikKcHJvcCA8LSBhcy5udW1lcmljKEFDX2NvbmZ1c2VfbWF0cml4X3Rlc3RfcHJvcCkKZGF0YSA8LSBleHBhbmQuZ3JpZCh4ID0geCwgeSA9IHkpICU+JSBiaW5kX2NvbHMocHJvcCA9IHByb3ApCnBsb3QgPC0gZ2dwbG90KGRhdGEsIGFlcyh4ID0geCwgeSA9IHksIGNvbG91ciA9IHByb3AsIHNpemUgPSBwcm9wKSkgKwogIGdlb21fcG9pbnQoKSsKICBzY2FsZV9zaXplX2NvbnRpbnVvdXMocmFuZ2UgPSBjKDAsIDEwKSkgKyAKICBsYWJzKHggPSBOVUxMLCB5ID0gTlVMTCkgKyB0aGVtZV9idygpCgpnZ3NhdmUoIi4vcGxvdHMvQUNtb2RlbF90cmFpbi5wbmciLCBwbG90ID0gcGxvdCwgZGV2aWNlID0gcG5nLCB3aWR0aCA9IDUsaGVpZ2h0ID0gNCkKCkFDX2NvbmZ1c2VfbWF0cml4X3Rlc3RfcHJvcAojUk9D5puy57q/CnhnYm9vc3Rfcm9jIDwtIHBST0M6Om11bHRpY2xhc3Mucm9jKEFDX3Rlc3RfZGF0YSRsYWJlbCwgcHJlZGljdF9BQ190ZXN0KSAj5aSa5YiG57G7Uk9DCnhnYm9vc3Rfcm9jW1siYXVjIl1dCgojIOiuoeeul0FSSSAKYWRqdXN0ZWRSYW5kSW5kZXgocHJlZGljdF9BQ190ZXN0LCBBQ190ZXN0X2RhdGEkbGFiZWwpCmBgYAoKCgpgYGB7cn0KZHMxIDwtIGRzMSAlPiUgRmluZE5laWdoYm9ycyhkaW1zID0gMToyMCkgJT4lIEZpbmRDbHVzdGVycyhyZXNvbHV0aW9uID0gMC4zKQp1bWFwcGxvdChkczEpCmRzMSA8LSBSZW5hbWVJZGVudHMoZHMxLCcwJyA9ICcwJywnMScgPScwJywnMic9JzEnLCczJz0nMicsJzQnID0gJzMnLCc1JyA9ICcxJykKdW1hcHBsb3QoZHMxKQpmKCJMVU0iLGRzMSkKYGBgCgpgYGB7cn0KdGVtcCA8LSBnZXRfZGF0YV90YWJsZShkczEsIGhpZ2h2YXIgPSBGLCB0eXBlID0gImRhdGEiKQpkczFfZGF0YSA8LSBtYXRyaXgoZGF0YT0wLCBucm93ID0gbGVuZ3RoKEFDX2dlbmVzJEZlYXR1cmUpLCBuY29sID0gbGVuZ3RoKGNvbG5hbWVzKHRlbXApKSwgYnlyb3cgPSBGQUxTRSwgZGltbmFtZXMgPSBsaXN0KEFDX2dlbmVzJEZlYXR1cmUsY29sbmFtZXModGVtcCkpKQpmb3IoaSBpbiBpbnRlcnNlY3QoQUNfZ2VuZXMkRmVhdHVyZSwgcm93bmFtZXModGVtcCkpKXsKICBkczFfZGF0YVtpLF0gPC0gdGVtcFtpLF0KfQpybSh0ZW1wKQpkczFfbGFiZWwgPC0gYXMubnVtZXJpYyhhcy5jaGFyYWN0ZXIoSWRlbnRzKGRzMSkpKQpjb2xuYW1lcyhkczFfZGF0YSkgPC0gTlVMTApkczFfdGVzdF9kYXRhIDwtIGxpc3QoZGF0YSA9IHQoYXMoZHMxX2RhdGEsImRnQ01hdHJpeCIpKSwgbGFiZWwgPSBkczFfbGFiZWwpCmRzMV90ZXN0IDwtIHhnYi5ETWF0cml4KGRhdGEgPSBkczFfdGVzdF9kYXRhJGRhdGEsbGFiZWwgPSBkczFfdGVzdF9kYXRhJGxhYmVsKQoKI+iuoeeul+a3t+a3huefqemYtQpwcmVkaWN0X2RzMV90ZXN0IDwtIHJvdW5kKHByZWRpY3QoYnN0X21vZGVsLCBuZXdkYXRhID0gZHMxX3Rlc3QpKQoKZHMxX2RhdGFfY29uZnVzZV9tYXRyaXhfdGVzdCA8LSB0YWJsZShkczFfdGVzdF9kYXRhJGxhYmVsLCBwcmVkaWN0X2RzMV90ZXN0LCBkbm49YygidHJ1ZSIsInByZSIpKQpkczFfZGF0YV9jb25mdXNlX21hdHJpeF90ZXN0X3Byb3AgPC0gcHJvcC50YWJsZShkczFfZGF0YV9jb25mdXNlX21hdHJpeF90ZXN0LDEpCgp4IDwtIGMoImRzMV8wIiwgImRzMV8xIiwgImRzMV8yIiwgImRzMV8zIikKeSA8LSBjKCJBQ18wIiwgIkFDXzEiLCAiQUNfMiIsICJBQ18zIikKCnByb3AgPC0gYXMubnVtZXJpYyhkczFfZGF0YV9jb25mdXNlX21hdHJpeF90ZXN0X3Byb3ApCmRhdGEgPC0gZXhwYW5kLmdyaWQoeCA9IHgsIHkgPSB5KSAlPiUgYmluZF9jb2xzKHByb3AgPSBwcm9wKQpwbG90IDwtIGdncGxvdChkYXRhLCBhZXMoeCA9IHgsIHkgPSB5LCBjb2xvdXIgPSBwcm9wLCBzaXplID0gcHJvcCkpICsKICBnZW9tX3BvaW50KCkrCiAgc2NhbGVfc2l6ZV9jb250aW51b3VzKHJhbmdlID0gYygwLCAxMCkpICsgCiAgbGFicyh4ID0gImNsdXN0ZXJzIiwgeSA9ICJpbmZlcnJlZCBmcm9tIikgKyB0aGVtZV9idygpCgpnZ3NhdmUoIi4vcGxvdHMvQUNtb2RlbF9kYXRhc2V0MS5wbmciLCBwbG90ID0gcGxvdCwgZGV2aWNlID0gcG5nLCB3aWR0aCA9IDUsaGVpZ2h0ID0gNCkKCgpkczFfZGF0YV9jb25mdXNlX21hdHJpeF90ZXN0CmRzMV9kYXRhX2NvbmZ1c2VfbWF0cml4X3Rlc3RfcHJvcCAgI+WIhuaekOWPkeiCsui9qOi/uQojUk9D5puy57q/CnhnYm9vc3Rfcm9jIDwtIHBST0M6Om11bHRpY2xhc3Mucm9jKGRzMV90ZXN0X2RhdGEkbGFiZWwsIHByZWRpY3RfZHMxX3Rlc3QpICPlpJrliIbnsbtST0MKeGdib29zdF9yb2NbWyJhdWMiXV0KCiMg6K6h566XQVJJIAphZGp1c3RlZFJhbmRJbmRleChwcmVkaWN0X2RzMV90ZXN0LCBkczFfdGVzdF9kYXRhJGxhYmVsKQpgYGAKCmBgYHtyfQpsYWJlbHMgPC0gbGFwcGx5KGxldmVscyhJZGVudHMoZHMyX0FDKSksIHBhc3RlMCwgIl9BQyIpICU+JSBhcy5jaGFyYWN0ZXIoKQpsYWJlbHMyIDwtIGxhcHBseShsZXZlbHMoSWRlbnRzKGRzMSkpLCBwYXN0ZTAsICJfZHMxIikgJT4lIGFzLmNoYXJhY3RlcigpCnNvdXJjZXMgPC0gcmVwKDA6KGxlbmd0aChsYWJlbHMpLTEpLCBlYWNoID0gbGVuZ3RoKGxhYmVsczIpKSAgI+azqOaEj+i/memHjOeahGVhY2jlkox0aW1lc+eahOWMuuWIqwpjb2xvcnMgPC0gcmVwKGNvbG9yc19saXN0WzE6bGVuZ3RoKGxhYmVscyldLCBlYWNoID0gbGVuZ3RoKGxhYmVsczIpKQp0YXJnZXRzIDwtIHJlcChsZW5ndGgobGFiZWxzKSswOihsZW5ndGgobGFiZWxzMiktMSksIHRpbWVzID0gbGVuZ3RoKGxhYmVscykpCgpwbG90X2x5KHR5cGUgPSAic2Fua2V5Iiwgb3JpZW50YXRpb24gPSAiaCIsCiAgICBub2RlID0gbGlzdCgKICAgICAgbGFiZWwgPSBjKGxhYmVscyxsYWJlbHMyKSwgCiAgICAgIGNvbG9yID0gY29sb3JzX2xpc3QsIHBhZCA9IDE1LCB0aGlja25lc3MgPSAzMCwKICAgICAgbGluZSA9IGxpc3QoCiAgICAgICAgY29sb3IgPSAiYmxhY2siLAogICAgICAgIHdpZHRoID0gMSkpLAogICAgbGluayA9IGxpc3QoCiAgICAgIHNvdXJjZSA9IHNvdXJjZXMsCiAgICAgIHRhcmdldCA9IHRhcmdldHMsCiAgICAgIHZhbHVlID0gIGFzLm51bWVyaWMoZHMxX2RhdGFfY29uZnVzZV9tYXRyaXhfdGVzdCksCiAgICAgIGNvbG9yID0gY29sb3JzCiAgICAgICkpCiNkczHmoLfmnKzmnIDlg49BQ18y55qE57uG6IOe5Lqa576kCmBgYAoKCgojIOWGoOeKtuWKqOiEieaVsOaNrumbhgpgYGB7cn0KZHMwIDwtIGRzMCAlPiUgRmluZE5laWdoYm9ycyhkaW1zID0gMToyMCkgJT4lIEZpbmRDbHVzdGVycyhyZXNvbHV0aW9uID0gMC4xKQp1bWFwcGxvdChkczApCmYoIlRBR0xOIixkczApCiMgZHMwX21hcmtlcnMgPC0gRmluZEFsbE1hcmtlcnMoZHMwLGxvZ2ZjLnRocmVzaG9sZCA9IDAuNywgbWluLmRpZmYucGN0ID0gMC4yKQpgYGAKCmBgYHtyfQpzZWxlY3RlZF9mZWF0dXJlcyA8LSBBQ19nZW5lcyRGZWF0dXJlCnRlbXAgPC0gZ2V0X2RhdGFfdGFibGUoZHMwLCBoaWdodmFyID0gRiwgdHlwZSA9ICJkYXRhIikKZHMwX2RhdGEgPC0gbWF0cml4KGRhdGE9MCwgbnJvdyA9IGxlbmd0aChzZWxlY3RlZF9mZWF0dXJlcyksIAogICAgICAgICAgICAgICAgICAgbmNvbCA9IGxlbmd0aChjb2xuYW1lcyh0ZW1wKSksIGJ5cm93ID0gRkFMU0UsIAogICAgICAgICAgICAgICAgICAgZGltbmFtZXMgPSBsaXN0KHNlbGVjdGVkX2ZlYXR1cmVzLGNvbG5hbWVzKHRlbXApKSkKZm9yKGkgaW4gaW50ZXJzZWN0KHNlbGVjdGVkX2ZlYXR1cmVzLHJvd25hbWVzKHRlbXApKSl7CiAgZHMwX2RhdGFbaSxdIDwtIHRlbXBbaSxdCn0Kcm0odGVtcCkKCmRzMF9sYWJlbCA8LSBhcy5udW1lcmljKGFzLmNoYXJhY3RlcihJZGVudHMoZHMwKSkpCmNvbG5hbWVzKGRzMF9kYXRhKSA8LSBOVUxMCmRzMF90ZXN0X2RhdGEgPC0gbGlzdChkYXRhID0gdChhcyhkczBfZGF0YSwiZGdDTWF0cml4IikpLCBsYWJlbCA9IGRzMF9sYWJlbCkKZHMwX3Rlc3QgPC0geGdiLkRNYXRyaXgoZGF0YSA9IGRzMF90ZXN0X2RhdGEkZGF0YSxsYWJlbCA9IGRzMF90ZXN0X2RhdGEkbGFiZWwpCgoj6K6h566X5re35reG55+p6Zi1CnByZWRpY3RfZHMwX3Rlc3QgPC0gcm91bmQocHJlZGljdChic3RfbW9kZWwsIG5ld2RhdGEgPSBkczBfdGVzdCkpCgpkczBfZGF0YV9jb25mdXNlX21hdHJpeF90ZXN0IDwtIHRhYmxlKGRzMF90ZXN0X2RhdGEkbGFiZWwsIHByZWRpY3RfZHMwX3Rlc3QsIGRubj1jKCJ0cnVlIiwicHJlIikpCmRzMF9kYXRhX2NvbmZ1c2VfbWF0cml4X3Rlc3RfcHJvcCA8LSBwcm9wLnRhYmxlKGRzMF9kYXRhX2NvbmZ1c2VfbWF0cml4X3Rlc3QsMSkKYGBgCgoKYGBge3J9CnggPC0gYygiZHMwXzAiLCAiZHMwXzEiLCAiZHMwXzIiLCAiZHMwXzMiLCAiZHMwXzQiKQp5IDwtIGMoIkFDXzAiLCAiQUNfMSIsICJBQ18yIikKCnByb3AgPC0gYXMubnVtZXJpYyhkczBfZGF0YV9jb25mdXNlX21hdHJpeF90ZXN0X3Byb3ApCmRhdGEgPC0gZXhwYW5kLmdyaWQoeCA9IHgsIHkgPSB5KSAlPiUgYmluZF9jb2xzKHByb3AgPSBwcm9wKQpwbG90IDwtIGdncGxvdChkYXRhLCBhZXMoeCA9IHgsIHkgPSB5LCBjb2xvdXIgPSBwcm9wLCBzaXplID0gcHJvcCkpICsKICBnZW9tX3BvaW50KCkrCiAgc2NhbGVfc2l6ZV9jb250aW51b3VzKHJhbmdlID0gYygwLCAxMCkpICsgCiAgbGFicyh4ID0gImNsdXN0ZXJzIiwgeSA9ICJpbmZlcnJlZCBmcm9tIikgKyB0aGVtZV9idygpCmdnc2F2ZSgiLi9wbG90cy9BQ21vZGVsX2h1bWFuY29yLnBuZyIsIHBsb3QgPSBwbG90LCBkZXZpY2UgPSBwbmcsIHdpZHRoID0gNSxoZWlnaHQgPSA0KQoKZHMwX2RhdGFfY29uZnVzZV9tYXRyaXhfdGVzdApkczBfZGF0YV9jb25mdXNlX21hdHJpeF90ZXN0X3Byb3AgICPliIbmnpDlj5HogrLovajov7kKCiNST0Pmm7Lnur8KeGdib29zdF9yb2MgPC0gcFJPQzo6bXVsdGljbGFzcy5yb2MoZHMwX3Rlc3RfZGF0YSRsYWJlbCwgcHJlZGljdF9kczBfdGVzdCkgI+WkmuWIhuexu1JPQwoKIyDorqHnrpdBUkkgCmFkanVzdGVkUmFuZEluZGV4KHByZWRpY3RfZHMwX3Rlc3QsIGRzMF90ZXN0X2RhdGEkbGFiZWwpCmBgYAoKCmBgYHtyfQpsYWJlbHMgPC0gbGFwcGx5KGxldmVscyhJZGVudHMoZHMyX0FDKSksIHBhc3RlMCwgIl9BQyIpICU+JSBhcy5jaGFyYWN0ZXIoKQpsYWJlbHMyIDwtIGxhcHBseShsZXZlbHMoSWRlbnRzKGRzMCkpLCBwYXN0ZTAsICJfZHMwIikgJT4lIGFzLmNoYXJhY3RlcigpCnNvdXJjZXMgPC0gcmVwKDA6KGxlbmd0aChsYWJlbHMpLTEpLCBlYWNoID0gbGVuZ3RoKGxhYmVsczIpKSAgI+azqOaEj+i/memHjOeahGVhY2jlkox0aW1lc+eahOWMuuWIqwpjb2xvcnMgPC0gcmVwKGNvbG9yc19saXN0WzE6bGVuZ3RoKGxhYmVscyldLCBlYWNoID0gbGVuZ3RoKGxhYmVsczIpKQp0YXJnZXRzIDwtIHJlcChsZW5ndGgobGFiZWxzKSswOihsZW5ndGgobGFiZWxzMiktMSksIHRpbWVzID0gbGVuZ3RoKGxhYmVscykpCgpwbG90X2x5KHR5cGUgPSAic2Fua2V5Iiwgb3JpZW50YXRpb24gPSAiaCIsCiAgICBub2RlID0gbGlzdCgKICAgICAgbGFiZWwgPSBjKGxhYmVscyxsYWJlbHMyKSwgCiAgICAgIGNvbG9yID0gY29sb3JzX2xpc3QsIHBhZCA9IDE1LCB0aGlja25lc3MgPSAzMCwKICAgICAgbGluZSA9IGxpc3QoCiAgICAgICAgY29sb3IgPSAiYmxhY2siLAogICAgICAgIHdpZHRoID0gMSkpLAogICAgbGluayA9IGxpc3QoCiAgICAgIHNvdXJjZSA9IHNvdXJjZXMsCiAgICAgIHRhcmdldCA9IHRhcmdldHMsCiAgICAgIHZhbHVlID0gIGFzLm51bWVyaWMoZHMwX2RhdGFfY29uZnVzZV9tYXRyaXhfdGVzdCksCiAgICAgIGNvbG9yID0gY29sb3JzCiAgICAgICkpCmBgYAoKCgoKYGBge3J9CiMgbG9hZCgiLi9pbml0LlJEYXRhIikKbXVsdGlfZmVhdHVyZXBsb3QoaGVhZChpbXBvcnRhbmNlMiw5KSRGZWF0dXJlLCBkczJfQUMpCm11bHRpX2ZlYXR1cmVwbG90KGhlYWQoaW1wb3J0YW5jZTIsOSkkRmVhdHVyZSwgZHMwKQptdWx0aV9mZWF0dXJlcGxvdChoZWFkKGltcG9ydGFuY2UyLDkpJEZlYXR1cmUsIGRzMSkKZigiTVlIMTEiLCBkczJfQUMpCnVtYXBwbG90KGRzMCkKYGBgCgoKIyDmt4vlt7Tnu4bog54KCmBgYHtyfQpseW1fZHMyIDwtIHN1YnNldChDQV9kYXRhc2V0MiwgaWRlbnRzID0gYygnMCcsJzQnLCc5JykpCklkZW50cyhseW1fZHMyKSA8LSBseW1fZHMyJGNvbmRpdGlvbnMKbHltX2RzMl9BQyA8LSBzdWJzZXQobHltX2RzMiwgaWRlbnRzID0gIkFDIikKbHltX2RzMl9QQSA8LSBzdWJzZXQobHltX2RzMiwgaWRlbnRzID0gIlBBIikKbHltX2RzMl9BQyA8LSBseW1fZHMyX0FDICU+JSBGaW5kTmVpZ2hib3JzKGRpbXMgPSAxOjIwKSAlPiUgRmluZENsdXN0ZXJzKHJlc29sdXRpb24gPSAwLjIpCnVtYXBwbG90KGx5bV9kczJfQUMpCmx5bV9kczJfUEEgPC0gbHltX2RzMl9QQSAlPiUgRmluZE5laWdoYm9ycyhkaW1zID0gMToyMCkgJT4lIEZpbmRDbHVzdGVycyhyZXNvbHV0aW9uID0gMC4yKQp1bWFwcGxvdChseW1fZHMyX1BBKQpgYGAKCiMjIOeUqFBB55qEbHlt6K6t57uDCmBgYHtyfQpseW1fUEFfZGF0YSA8LSBnZXRfZGF0YV90YWJsZShseW1fZHMyX1BBLCBoaWdodmFyID0gRiwgdHlwZSA9ICJkYXRhIikKbHltX1BBX2xhYmVsIDwtIGFzLm51bWVyaWMoYXMuY2hhcmFjdGVyKElkZW50cyhseW1fZHMyX1BBKSkpCgpzZXQuc2VlZCg3KQppbmRleCA8LSBjKDE6ZGltKGx5bV9QQV9kYXRhKVsyXSkgJT4lIHNhbXBsZShjZWlsaW5nKDAuMypkaW0obHltX1BBX2RhdGEpWzJdKSwgcmVwbGFjZSA9IEYsIHByb2IgPSBOVUxMKQpjb2xuYW1lcyhseW1fUEFfZGF0YSkgPC0gTlVMTApseW1fUEFfdHJhaW5fZGF0YSA8LSBsaXN0KGRhdGEgPSB0KGFzKGx5bV9QQV9kYXRhWywtaW5kZXhdLCJkZ0NNYXRyaXgiKSksIGxhYmVsID0gbHltX1BBX2xhYmVsWy1pbmRleF0pCmx5bV9QQV90ZXN0X2RhdGEgPC0gbGlzdChkYXRhID0gdChhcyhseW1fUEFfZGF0YVssaW5kZXhdLCJkZ0NNYXRyaXgiKSksIGxhYmVsID0gbHltX1BBX2xhYmVsW2luZGV4XSkKCmx5bV9QQV90cmFpbiA8LSB4Z2IuRE1hdHJpeChkYXRhID0gbHltX1BBX3RyYWluX2RhdGEkZGF0YSxsYWJlbCA9IGx5bV9QQV90cmFpbl9kYXRhJGxhYmVsKQpseW1fUEFfdGVzdCA8LSB4Z2IuRE1hdHJpeChkYXRhID0gbHltX1BBX3Rlc3RfZGF0YSRkYXRhLGxhYmVsID0gbHltX1BBX3Rlc3RfZGF0YSRsYWJlbCkKCndhdGNobGlzdCA8LSBsaXN0KHRyYWluID0gbHltX1BBX3RyYWluLCBldmFsID0gbHltX1BBX3Rlc3QpCnhnYl9wYXJhbSA8LSBsaXN0KGV0YSA9IDAuMiwgbWF4X2RlcHRoID0gNiwgCiAgICAgICAgICAgICAgICAgIHN1YnNhbXBsZSA9IDAuNiwgIG51bV9jbGFzcyA9IGxlbmd0aCh0YWJsZShJZGVudHMobHltX2RzMl9QQSkpKSwKICAgICAgICAgICAgICAgICAgb2JqZWN0aXZlID0gIm11bHRpOnNvZnRtYXgiLCBldmFsX21ldHJpYyA9ICdtbG9nbG9zcycpCgpic3RfbW9kZWwgPC0geGdiLnRyYWluKHhnYl9wYXJhbSwgbHltX1BBX3RyYWluLCBucm91bmRzID0gMjAwLCB3YXRjaGxpc3QsIHZlcmJvc2UgPSAxKQpgYGAKCgpgYGB7cn0KIyDnibnlvoHmj5Dlj5YKaW1wb3J0YW5jZSA8LSB4Z2IuaW1wb3J0YW5jZShjb2xuYW1lcyhseW1fUEFfdHJhaW4pLCBtb2RlbCA9IGJzdF9tb2RlbCkKaGVhZChpbXBvcnRhbmNlKQp4Z2IuZ2dwbG90LmltcG9ydGFuY2UoaGVhZChpbXBvcnRhbmNlLDIwKSxuX2NsdXN0ZXJzID0gMSkrdGhlbWVfYncoKSAj6L+Z5LiqY2x1c3RlcuWSjOWIhuexu+ayoeacieWFs+ezuwpseW1fUEFfZ2VuZXMgPC0gaGVhZChpbXBvcnRhbmNlLCA1MDApICMj6YCJ5oupdG9wNTAwCgp3cml0ZS5jc3YobHltX1BBX2dlbmVzLCIuL2RhdGF0YWJsZS9seW1fUEFfZmVhdHVyZXMuY3N2Iiwgcm93Lm5hbWVzID0gRikKI+a3t+a3huefqemYtQpwcmVkaWN0X2x5bV9QQV90ZXN0IDwtIHJvdW5kKHByZWRpY3QoYnN0X21vZGVsLCBuZXdkYXRhID0gbHltX1BBX3Rlc3QpKQoKbHltX1BBX2NvbmZ1c2VfbWF0cml4X3Rlc3QgPC0gdGFibGUobHltX1BBX3Rlc3RfZGF0YSRsYWJlbCwgcHJlZGljdF9seW1fUEFfdGVzdCwgZG5uPWMoInRydWUiLCJwcmUiKSkKbHltX1BBX2NvbmZ1c2VfbWF0cml4X3Rlc3RfcHJvcCA8LSBwcm9wLnRhYmxlKGx5bV9QQV9jb25mdXNlX21hdHJpeF90ZXN0LCAxKQpseW1fUEFfY29uZnVzZV9tYXRyaXhfdGVzdF9wcm9wCgp4IDwtIGMoIlBBX2x5bV8wIiwgIlBBX2x5bV8xIiwgIlBBX2x5bV8yIiwgIlBBX2x5bV8zIiwgIlBBX2x5bV80IiwgIlBBX2x5bV81IikKeSA8LSBjKCJQQV9seW1fMCIsICJQQV9seW1fMSIsICJQQV9seW1fMiIsICJQQV9seW1fMyIsICJQQV9seW1fNCIsICJQQV9seW1fNSIpCgpwcm9wIDwtIGFzLm51bWVyaWMobHltX1BBX2NvbmZ1c2VfbWF0cml4X3Rlc3RfcHJvcCkKZGF0YSA8LSBleHBhbmQuZ3JpZCh4ID0geCwgeSA9IHkpICU+JSBiaW5kX2NvbHMocHJvcCA9IHByb3ApCnBsb3QgPC0gZ2dwbG90KGRhdGEsIGFlcyh4ID0geCwgeSA9IHksIGNvbG91ciA9IHByb3AsIHNpemUgPSBwcm9wKSkgKwogIGdlb21fcG9pbnQoKSsKICBzY2FsZV9zaXplX2NvbnRpbnVvdXMocmFuZ2UgPSBjKDAsIDEwKSkgKyAKICBsYWJzKHggPSAiY2x1c3RlcnMiLCB5ID0gImluZmVycmVkIGZyb20iKSArIHRoZW1lX2J3KCkKZ2dzYXZlKCIuL3Bsb3RzL1BBbHltbW9kZWwucG5nIiwgcGxvdCA9IHBsb3QsIGRldmljZSA9IHBuZywgd2lkdGggPSA3LGhlaWdodCA9NikKYGBgCgoKIyMg55SoQUPnmoRseW3pqozor4EKYGBge3J9Cmx5bV9BQ19kYXRhIDwtIGdldF9kYXRhX3RhYmxlKGx5bV9kczJfQUMsIGhpZ2h2YXIgPSBGLCB0eXBlID0gImRhdGEiKQpseW1fQUNfbGFiZWwgPC0gYXMubnVtZXJpYyhhcy5jaGFyYWN0ZXIoSWRlbnRzKGx5bV9kczJfQUMpKSkKCmNvbG5hbWVzKGx5bV9BQ19kYXRhKSA8LSBOVUxMCgpseW1fQUNfdGVzdF9kYXRhIDwtIGxpc3QoZGF0YSA9IHQoYXMobHltX0FDX2RhdGEsImRnQ01hdHJpeCIpKSwgbGFiZWwgPSBseW1fQUNfbGFiZWwpCgpseW1fQUNfdGVzdCA8LSB4Z2IuRE1hdHJpeChkYXRhID0gbHltX0FDX3Rlc3RfZGF0YSRkYXRhLGxhYmVsID0gbHltX0FDX3Rlc3RfZGF0YSRsYWJlbCkKIyBtdWx0aV9mZWF0dXJlcGxvdChoZWFkKGltcG9ydGFuY2UsOSkkRmVhdHVyZSxseW1fZHMyX0FDKQoKI+a3t+a3huefqemYtQpwcmVkaWN0X2x5bV9BQ190ZXN0IDwtIHJvdW5kKHByZWRpY3QoYnN0X21vZGVsLCBuZXdkYXRhID0gbHltX0FDX3Rlc3QpKQoKbHltX0FDX2NvbmZ1c2VfbWF0cml4X3Rlc3QgPC0gdGFibGUobHltX0FDX3Rlc3RfZGF0YSRsYWJlbCwgcHJlZGljdF9seW1fQUNfdGVzdCwgZG5uPWMoInRydWUiLCJwcmUiKSkKbHltX0FDX2NvbmZ1c2VfbWF0cml4X3Rlc3RfcHJvcCA8LSBwcm9wLnRhYmxlKGx5bV9BQ19jb25mdXNlX21hdHJpeF90ZXN0LCAxKQpseW1fQUNfY29uZnVzZV9tYXRyaXhfdGVzdF9wcm9wCgoKeCA8LSBjKCJQQV9seW1fMCIsICJQQV9seW1fMSIsICJQQV9seW1fMiIsICJQQV9seW1fMyIsICJQQV9seW1fNCIsICJQQV9seW1fNSIpCnkgPC0gYygiUEFfbHltXzAiLCAiUEFfbHltXzEiLCAiUEFfbHltXzIiLCAiUEFfbHltXzMiLCAiUEFfbHltXzQiKQoKcHJvcCA8LSBhcy5udW1lcmljKGx5bV9BQ19jb25mdXNlX21hdHJpeF90ZXN0X3Byb3ApCmRhdGEgPC0gZXhwYW5kLmdyaWQoeCA9IHgsIHkgPSB5KSAlPiUgYmluZF9jb2xzKHByb3AgPSBwcm9wKQpwbG90IDwtIGdncGxvdChkYXRhLCBhZXMoeCA9IHgsIHkgPSB5LCBjb2xvdXIgPSBwcm9wLCBzaXplID0gcHJvcCkpICsKICBnZW9tX3BvaW50KCkrCiAgc2NhbGVfc2l6ZV9jb250aW51b3VzKHJhbmdlID0gYygwLCAxMCkpICsgCiAgbGFicyh4ID0gImNsdXN0ZXJzIiwgeSA9ICJpbmZlcnJlZCBmcm9tIikgKyB0aGVtZV9idygpCmdnc2F2ZSgiLi9wbG90cy9QQWx5bW1vZGVsX0FDLnBuZyIsIHBsb3QgPSBwbG90LCBkZXZpY2UgPSBwbmcsIHdpZHRoID0gNyxoZWlnaHQgPTYpCnhnYm9vc3Rfcm9jW1siYXVjIl1dCmFkanVzdGVkUmFuZEluZGV4KHByZWRpY3RfbHltX0FDX3Rlc3QsIGx5bV9BQ190ZXN0X2RhdGEkbGFiZWwpCmx5bV9BQ19jb25mdXNlX21hdHJpeF90ZXN0X3Byb3AKYGBgCgoKYGBge3J9CmxhYmVscyA8LSBsYXBwbHkobGV2ZWxzKElkZW50cyhseW1fZHMyX1BBKSksIHBhc3RlMCwgIl9seW1QQSIpICU+JSBhcy5jaGFyYWN0ZXIoKQpsYWJlbHMyIDwtIGxhcHBseShsZXZlbHMoSWRlbnRzKGx5bV9kczJfQUMpKSwgcGFzdGUwLCAiX2x5bUFDIikgJT4lIGFzLmNoYXJhY3RlcigpCnNvdXJjZXMgPC0gcmVwKDA6NSwgZWFjaCA9IDUpICAj5rOo5oSP6L+Z6YeM55qEZWFjaOWSjHRpbWVz55qE5Yy65YirCmNvbG9ycyA8LSByZXAoY29sb3JzX2xpc3RbMTo2XSwgZWFjaCA9IDUpCnRhcmdldHMgPC0gcmVwKDY6MTAsIHRpbWVzID0gNikKCnBsb3RfbHkodHlwZSA9ICJzYW5rZXkiLCBvcmllbnRhdGlvbiA9ICJoIiwKICAgIG5vZGUgPSBsaXN0KAogICAgICBsYWJlbCA9IGMobGFiZWxzLGxhYmVsczIpLCAKICAgICAgY29sb3IgPSBjb2xvcnNfbGlzdCwgcGFkID0gMTUsIHRoaWNrbmVzcyA9IDMwLAogICAgICBsaW5lID0gbGlzdCgKICAgICAgICBjb2xvciA9ICJibGFjayIsCiAgICAgICAgd2lkdGggPSAxKSksCiAgICBsaW5rID0gbGlzdCgKICAgICAgc291cmNlID0gc291cmNlcywKICAgICAgdGFyZ2V0ID0gdGFyZ2V0cywKICAgICAgdmFsdWUgPSAgYXMubnVtZXJpYyhseW1fQUNfY29uZnVzZV9tYXRyaXhfdGVzdCksCiAgICAgIGNvbG9yID0gY29sb3JzCiAgICAgICkpCgoKdW1hcHBsb3QobHltX2RzMl9BQykKdW1hcHBsb3QobHltX2RzMl9QQSkKYGBgCgpBZGQgYSBuZXcgY2h1bmsgYnkgY2xpY2tpbmcgdGhlICpJbnNlcnQgQ2h1bmsqIGJ1dHRvbiBvbiB0aGUgdG9vbGJhciBvciBieSBwcmVzc2luZyAqQ3RybCtBbHQrSSouCgpXaGVuIHlvdSBzYXZlIHRoZSBub3RlYm9vaywgYW4gSFRNTCBmaWxlIGNvbnRhaW5pbmcgdGhlIGNvZGUgYW5kIG91dHB1dCB3aWxsIGJlIHNhdmVkIGFsb25nc2lkZSBpdCAoY2xpY2sgdGhlICpQcmV2aWV3KiBidXR0b24gb3IgcHJlc3MgKkN0cmwrU2hpZnQrSyogdG8gcHJldmlldyB0aGUgSFRNTCBmaWxlKS4KClRoZSBwcmV2aWV3IHNob3dzIHlvdSBhIHJlbmRlcmVkIEhUTUwgY29weSBvZiB0aGUgY29udGVudHMgb2YgdGhlIGVkaXRvci4gQ29uc2VxdWVudGx5LCB1bmxpa2UgKktuaXQqLCAqUHJldmlldyogZG9lcyBub3QgcnVuIGFueSBSIGNvZGUgY2h1bmtzLiBJbnN0ZWFkLCB0aGUgb3V0cHV0IG9mIHRoZSBjaHVuayB3aGVuIGl0IHdhcyBsYXN0IHJ1biBpbiB0aGUgZWRpdG9yIGlzIGRpc3BsYXllZC4K